


package NetworkManager;




import java.io.*;
import java.net.*;
import java.util.Observable;
import MiddleLayer.OtherClass.*;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;




public class MultiThreadChatServer implements Observer{

    MyObservable _observable = new MyObservable();

    // Declaration section:
    // declare a server socket and a client socket for the server
    // declare an input and an output stream

    static  Socket clientSocket = null;
    static  ServerSocket serverSocket = null;
    static private int iNumOfThread = 4;// This chat server can accept up to 4 clients' connections
    private int iNumOfClient = 0;//Số lượng client connect vào server
    private boolean isAcceptConnect = true;

    

    static  clientThread threadPools[] = new clientThread[iNumOfThread];

    //Chứa dữ liệu nhận được từ client: Đã được chuyển từ chuỗi về kiểu cấu trúc
    //Đẩy lên lớp trên để xử lý
    public CommandStruct clientCMDReceiver[] = new CommandStruct[iNumOfThread];
    
    //Khung chat
    public CommandStruct clientChatReceiver[] = new CommandStruct[iNumOfThread];


    public void initServer(int port_number)
    {
        // Initialization section:
	// Try to open a server socket on port port_number (default 2222)
	// Note that we can'threadPools choose a port less than 1023 if we are not
	// privileged users (root)

        try {
	    serverSocket = new ServerSocket(port_number, iNumOfThread);
        }
        catch (IOException e)
	    {System.out.println(e);}

	// Create a socket object from the ServerSocket to listen and accept
	// connections.
	// Open input and output streams for this socket will be created in
	// client's thread since every client is served by the server in
	// an individual thread

        if(serverSocket.isBound())
        {
            System.out.println("Server init at port number " + port_number + ".");

            while(isAcceptConnect()){
                try {
                    clientSocket = serverSocket.accept();
                    for(int i = 0; i < iNumOfThread; i++){
                        if(threadPools[i] == null){

                            threadPools[i] = new clientThread(clientSocket,threadPools, iNumOfThread, clientCMDReceiver, clientChatReceiver, i);
                            threadPools[i].addObserver(this);
                            iNumOfClient++;
                            threadPools[i].start();
                            break;
                        }
                    }
                }
                catch (IOException e) {
                    System.out.println(e);}
            }
        }
    }

    public MyObservable observable() {
        return _observable;
    }


    //Có dữ liệu đến, đẩy dữ liệu lên cho lớp bên trên xử lý
    public void update(Observable o, Object arg) {
        notifyNewMessageFromClient(arg);
    }

    /*
     * Báo cho bên trên biết là có dữ liệu tới từ thread số mấy
     * Bên trên sẽ lấy dữ liệu từ trong clientCMDReceiver[]
     * arg chứa giá trị số nguyên cho biết là thread số mấy có dữ liệu tới
     */
    public void notifyNewMessageFromClient(Object arg)
    {
        this._observable.setChanged();           // set changed flag
        this._observable.notifyObservers(arg);  // do notification
    }

    /**
     * Gửi dữ liệu đến thread số mấy, nhớ kiểm tra kĩ trước khi gọi hàm này
     * Dữ liệu này đã có thêm mã lệnh ở phía trước
     */
     public void sendDataToThreadNumber(int iOrderOfThread, CommandStruct cmdStruct){
         if (threadPools[iOrderOfThread] != null){
             threadPools[iOrderOfThread].os.println(cmdStruct.toString());
             threadPools[iOrderOfThread].os.flush();
         }
     }

     /**
      * Gửi dữ liệu text đến các thread khác, thường là chỉ có dữ liệu chat
      */
     public void sendDataToAllThread(CommandStruct cmdStruct)
     {
         for(int i = 0; i < iNumOfThread; i++){
		if (threadPools[i] != null)
                {
                    threadPools[i].os.println(cmdStruct.toString());
                    threadPools[i].os.flush();
             }
         }

     }

     /**
      * Gửi dữ liệu text đến các thread khác, thường là gửi dữ liệu chat
      */
     public void sendDataToOtherThread(CommandStruct cmdStruct, int iClientWillSend) {
         for(int i = 0; i < iNumOfThread; i++)
             if (threadPools[i] != null && i != iClientWillSend)
             {
                    threadPools[i].os.println(cmdStruct.toString());
                    threadPools[i].os.flush();
             }
     }

     /**
      * Ngắt kết nối với tất cả client
      */
     public void disconnectClients()
     {
         for(int i = 0; i < iNumOfThread; i++)
             if (threadPools[i] != null)
             {
                 //Gửi lệnh ngắt tới server
                 sendDataToThreadNumber(i,
                         new CommandStruct(
                             CommandStruct.MessageType.Quit,
                             ""));
                 
                 threadPools[i].closed = true;
             }
     }


    public synchronized void addObserver(Observer o)
    {
        this._observable.addObserver(o);
    }

    /**
     * @return the isAcceptConnect
     */
    public boolean isAcceptConnect() {
        return isAcceptConnect;
    }

    /**
     * Không chấp nhận kết nối mới nữa
     */
    public void setNotAcceptConnect() {
        this.isAcceptConnect = false;
    }

    /**
     * @return the iNumOfClient
     */
    public int getNumOfClient() {
        return iNumOfClient;
    }

//     /**
//     * Chỉ gọi đến hàm này khi có dữ liệu cần gửi đi
//     * Dữ liệu tự động chuyển đổi về kiểu string
//     */
//
//    public void sendMesssage(CommandStruct cmdStruct, int iOrderOfClient)
//    {
//
//        //Nếu là thông điệp chat thì gửi đi tới tất cả
//        if(cmdStruct.isChatMessage() && iOrderOfClient != -1)
//            sendDataToOtherThread(cmdStruct.toString(), iOrderOfClient);
//
//        //Nếu là lệnh thì gửi riêng đến client
//        if(cmdStruct.isCommandMessage())
//            sendDataToThreadNumber(iOrderOfClient, cmdStruct.toString());
//
//    }

}



/**
 * This client thread opens the input and the output streams for a particular client,
 * ask the client's name, informs all the clients currently connected to the
 * server about the fact that a new client has joined the chat room,
 * and as long as it receive data, echos that data back to all other clients.
 * When the client leaves the chat room this thread informs also all the
 * clients about that and terminates.
 */

class clientThread extends Thread{

    int iTimeToSleep = 50;//milis
    public boolean closed = false;
    
    MyObservable _observable = new MyObservable();
    
    DataInputStream is = null;
    PrintStream os = null;

    Socket clientSocket = null;

    clientThread threadPools[];
    private int iOrderOfThread = -1;//Cho biết thread này là thread số mấy
    private int iNumOfThread = 4;// This chat server can accept up to 4 clients' connections
    CommandStruct clientCMDReceiver[] = null; //Cho biết dữ liệu sẽ từ client nào gửi tới
    CommandStruct clientChatReceiver[] = new CommandStruct[iNumOfThread];


    public String strTextReceived = null;

    public clientThread(Socket clientSocket, clientThread[] t, int iNumOfThread, 
            CommandStruct clientCMDReceiver[], CommandStruct clientChatReceiver[], int iOrderOfThread){
	this.clientSocket = clientSocket;
        this.threadPools = t;
        this.iNumOfThread = iNumOfThread;
        this.iOrderOfThread = iOrderOfThread;
        this.clientCMDReceiver = clientCMDReceiver;
        this.clientChatReceiver = clientChatReceiver;
    }




    @Override
    public void run()
    {
	try{
	    is = new DataInputStream(clientSocket.getInputStream());
	    os = new PrintStream(clientSocket.getOutputStream());

            //Mặc định theo kịch bản, connect xong là gửi ngay tên liền
            //Gửi thông điệp lên trên cho biết là đã có kết nối tới người chơi
            //Thông tin người chơi kết nối tới nằm trong strTextReceived
            notifyRawTextHasArrived(is.readLine());

	    while (!this.closed) {

                if((strTextReceived = is.readLine()) != null)
                {
                    //Đọc dữ liệu từ bên dưới truyền lên, biết là của client nào.
                    notifyRawTextHasArrived(strTextReceived);



                    //Gửi dữ liệu cho tất cả các client còn lại...
                    //hàm sendDataToThreadNumber sẽ gửi dữ liệu acb đến thread số 1 hay client số 1
                    //Có tối đa 4 client: 0, 1, 2, 3.
                    //Chỉ cần kiểm tra ở đây thì sẽ biết dữ liệu mình sắp gửi cho lai
                    //sendDataToThreadNumber(1, "acb");
                    System.out.println("Dữ liệu nhận được từ client số " + iOrderOfThread + ": \"" + strTextReceived + "\"");
                }

                try {
                    this.sleep(this.iTimeToSleep);
                } catch (InterruptedException ex) {
                    Logger.getLogger(clientThread.class.getName()).log(Level.SEVERE, null, ex);
                }

	    }
            
	    // Clean up:
	    // Set to null the current thread variable such that other client could
	    // be accepted by the server

	    for(int i = 0; i < iNumOfThread; i++)
		if (threadPools[i] ==  this)
                    threadPools[i] = null;

	    // close the output stream
	    // close the input stream
	    // close the socket

	    is.close();
	    os.close();
	    clientSocket.close();
	}
	catch(IOException e){};
    }

    /*
     * Thông báo cho biết dữ liệu đã được gửi đến
     * Chuyển đổi thành CommandStruct để gửi lên bên trên
     */
    public void notifyRawTextHasArrived(String rawText) {

        int iMessageType = 0;//sử dụng trick ở đây, nếu là chat thì dữ liệu bên trên nhận được
        //là iOrderOfThread * 10 + 0; còn nếu là dữ liệu cmd thì là * 10 + 1
        if(this.clientCMDReceiver[this.iOrderOfThread] == null)
        {
            if( !CommandStruct.isChatMessage(rawText) )
            {
                this.clientCMDReceiver[this.iOrderOfThread] = new CommandStruct(rawText);
                iMessageType = 1;
            }
            else
            {
                this.clientChatReceiver[this.iOrderOfThread] = new CommandStruct(rawText);
                iMessageType = 0;
            }
        }
        else
        {
            if( !CommandStruct.isChatMessage(rawText) )
            {
                if(this.clientCMDReceiver[this.iOrderOfThread] != null)
                    this.clientCMDReceiver[this.iOrderOfThread].changeCommand(rawText);
                else
                    this.clientCMDReceiver[this.iOrderOfThread] = new CommandStruct(rawText);
                iMessageType = 1;
            }
            else
            {
                if(this.clientChatReceiver[this.iOrderOfThread] != null)
                    this.clientChatReceiver[this.iOrderOfThread].changeCommand(rawText);
                else
                    this.clientChatReceiver[this.iOrderOfThread] = new CommandStruct(rawText);

                iMessageType = 0;
            }
        }

        iMessageType = (this.iOrderOfThread + 1) * 10 + iMessageType;//trick để có thể liên lạc được với nhau
        this._observable.setChanged();                          // set changed flag
        this._observable.notifyObservers(iMessageType);  // do notification
    }

    public synchronized void addObserver(Observer o)
    {
        this._observable.addObserver(o);
    }
        
 }